package hermesV3.services.HubService;

import it.unicam.cs.cosy.bioshape.core.components.GlobalCoordinator;
import it.unicam.cs.cosy.bioshape.core.components.LocalCoordinator;
import it.unicam.cs.cosy.bioshape.core.util.Messages;
import hermesV3.Message;
import hermesV3.PlaceAddress;
import hermesV3.agent.Identifier;
import hermesV3.agent.InteractionContainer;
import hermesV3.agent.MigrationException;
import hermesV3.agent.ServiceAgent;
import hermesV3.agent.UserAgent;
import hermesV3.protocols.SynProtocol;
import hermesV3.services.HubAgent;
import hermesV3.util.HermesLogger;

/**
 * Started from the {@code HubAgent}. It moves from its {@code Place} to the
 * {@code Place} of the {@code GlobalCoordinator} and establishes a
 * communication with it.<br>
 * It maintains {@code Identifier}s for the {@code GlobalCoordinator}, the
 * {@code SpaceManager} and the {@code SimulationAgent} of the
 * {@code GlobalCoordinator}. If the negotiation with the
 * {@code GlobalCoordinator} is successful, it generates the
 * {@code LocalCoordinator} and passes to it the {@code Identifier}s so that
 * it can manage the simulation.<br>
 * Note that the {@code PeerAgent} maintains also an {@code Identifier} to
 * the {@code SimulationAgent} of its simulation node {@code Place}. If
 * something goes wrong during the negotiation, the {@code PeerAgent} can update
 * the GUI of its node through such agent.
 * 
 * @author Federico Buti, Gaston Alanis, Matteo Micheletti, Andrea Piermarteri
 */
public class PeerAgent extends UserAgent {

	private static final long serialVersionUID = -6397721852552333662L;
	private static final String HUB = "HUB";
	private Identifier gcID;
	private PlaceAddress hubplace;
	private InteractionContainer ic;
	private Identifier smID;
	private int retries;
	private Identifier iID;
	private Identifier giID;
	private boolean done;

	/**
	 * Builds a {@code PeerAgent} to set up simulation with the
	 * {@code GlobalCoordinator}.
	 * 
	 * @param iID
	 *            {@code Identifier} for the {@code SimulationAgent} of this
	 *            {@code PeerAgent}. It is passed by the {@code HubAgent} of the
	 *            Hermes {@code Place} on which the same agent is born.
	 * @param hubplace
	 *            {@code PlaceAddress} of the {@code HubServiceAgent}
	 * @param retries
	 *            Number of connection attempts to the {@code HubServiceAgent}
	 * 
	 * @see GlobalCoordinator
	 * @see HubAgent
	 * @see PlaceAddress
	 */
	public PeerAgent(Identifier iID, PlaceAddress hubplace, Integer retries) {
		super("PeerAgent");
		this.iID = iID;
		this.hubplace = hubplace;
		this.retries = retries.intValue();
	}

	/**
	 * The {@link PeerAgent} tries to Move to the {@link HubAgent} Place. <br>
	 * This is necessary for the communication between them.
	 */
	@Override
	public void init() {
		reception();
		HermesLogger.log(HermesLogger.INFO, this.getAgentName()
				+ " - Peer correctly initiated. Connecting to the HubAgent...");
		// Inform of its existence to the SimulationAgent OF THIS NODE
		Messages.sendSyn(this, iID);
		//
		// Connection attempts to the HUB service...
		int tries = 0;
		do {
			if (checkGUI() || done)
				return;
			HermesLogger.log(HermesLogger.INFO, this.getAgentName()
					+ " - connection attempt # " + ++tries);
			try {
				Thread.sleep(500); // delay to catch local nodes stops
				move(hubplace, "moveToHubAgent");
			} catch (Exception me) {
				// if it is the last try, ABORT THE PEER PLACE and communicate
				// to its SimulationAgent to update the GUI
				HermesLogger.log(HermesLogger.ERROR, this.getAgentName()
						+ " - unable to connect to the " + "HUB place..."
						+ "Attempt #" + tries);
				if (tries == retries) {
					HermesLogger.log(HermesLogger.ERROR, this.getAgentName()
							+ " - simulation aborted...");
					Messages.sendAck(this, iID);
				}
			}
		} while (retries > tries);
	}

	/**
	 * Checks messages from the {@code SimulationAgent} for GUI interactions.
	 * The {@code SynProtocol} interaction is used to represent the play/pause
	 * of the simulation and the stop. Interactions {@code INTERACTIONSYN} and
	 * {@code INTERACTIONACK} are used respectively.
	 * 
	 * @return {@code true} if the simulation was stopped ({@code INTERACTIONACK}
	 *         ), false otherwise ({@code INTERACTIONSYN}).
	 */
	private boolean checkGUI() {
		Message<?> msg = getMessageAsynch(iID);
		if (msg == null)
			return false;
		// InteractionContainer ic = (InteractionContainer) msg.getObject();
		// if (ic.getInteractionName().equals(SynProtocol.INTERACTIONSYN)) {
		// HermesLogger.log(HermesLogger.INFO, this.getAgentName()
		// + " - Connection attempts paused by the user...");
		// msg = getMessageSynch(iID);
		// ic = (InteractionContainer) msg.getObject();
		// if (ic.getInteractionName().equals(SynProtocol.INTERACTIONSYN))
		// return false;
		// }// else
		HermesLogger.log(HermesLogger.INFO, this.getAgentName()
				+ " - Connection attempts interrupted by the user. Simulation"
				+ " aborted on this node...");
		return true;
	}

	/**
	 * The {@link PeerAgent} succeeds in Moving to the {@link HubAgent} Place. <br>
	 * Without moving, communication would be impossible across places.
	 */
	public void moveToHubAgent() {
		reception();
		HermesLogger.log(HermesLogger.INFO, this.getAgentName()
				+ " - Successfully moved to the HUB place " + hubplace);
		PlaceAddress peerPlaceToRegister = this.getIdentifier()
				.getBornPlaceAddress();
		/* Find the peer to peer Service Identificator */
		Identifier serviceId = this.getServiceIdentifier(HUB);
		if (serviceId == null) {
			HermesLogger.log(HermesLogger.ERROR, this.getAgentName()
					+ " - something wrong with the HUB service agent...");
			HermesLogger.log(HermesLogger.ERROR, this.getAgentName()
					+ " - please review your configuration, PeerAgent "
					+ "aborted...");
			return;
		}
		// HUB IS UP --> sending to it the PEER PlaceAddress
		SynProtocol synProtocol = (SynProtocol) getServiceProtocol(serviceId);
		ic = new InteractionContainer(synProtocol);
		Object[] params = new Object[1];
		params[0] = peerPlaceToRegister;
		ic.makeInteraction(SynProtocol.INTERACTIONSYN, params);
		Messages.sendServiceInteraction(this, serviceId, new SynProtocol(),
				SynProtocol.INTERACTIONSYN, params);
		// Waiting answer from the HUB (actually a PeeringHandler thread)
		Message<?> response = Messages.receiveSynAck(this, serviceId);
		if (response == null) {
			HermesLogger.log(HermesLogger.ERROR, this.getAgentName()
					+ " - something wrong while receiving messages from the"
					+ "HubAgent, PeerAgent aborted...");
			return;
		}
		ic = (InteractionContainer) response.getObject();
		params = (Object[]) ic.getParameter(SynProtocol.INTERACTIONSYNACK);
		gcID = (Identifier) params[0];
		smID = (Identifier) params[1];
		giID = (Identifier) params[2];
		if (gcID == null || smID == null) {
			HermesLogger.log(HermesLogger.ERROR, this.getAgentName()
					+ " - something wrong while receiving GlobalCoordinator "
					+ "and SpaceManager identificators from the HubAgent, "
					+ "PeerAgent aborted...");
			return;
		}
		// move back;
		try {
			this.move(peerPlaceToRegister, "backToPeerAgent");
		} catch (MigrationException me) {
			HermesLogger.log(HermesLogger.ERROR, this.getAgentName()
					+ " - unable to connect to the original Hermes Place. "
					+ "PeerAgent execution terminated");
		}
	}

	/**
	 * Called when the {@link PeerAgent} gets back to its place, after the
	 * communication with the {@link ServiceAgent} occurred.<br>
	 * Now that the peer knows the GlobalCoordinator, it can communicate with it
	 * and ask for {@code LocalCoordinator} creation: if not necessary the peer
	 * simply dies.
	 * 
	 * @see it.unicam.cs.cosy.bioshape.core.components.GlobalCoordinator
	 * @see it.unicam.cs.cosy.bioshape.core.components.LocalCoordinator
	 * 
	 */
	public void backToPeerAgent() {
		HermesLogger.log(HermesLogger.INFO, this.getAgentName()
				+ " - Communication accomplished...");
		// Communicate its existence to the GLOBAL SimulationAgent (if this is
		// not the GlobalCoordinator node)
		if (!giID.equals(iID))
			Messages.sendSyn(this, giID);
		// Ask the Global for local coordinator creation (creation could be
		// useless if already enough LocalCoordinators have been created!)
		HermesLogger.log(HermesLogger.INFO, this.getAgentName()
				+ " - Checking Local Coordinator creation...");
		Messages.sendAck(this, gcID);
		// Waiting for an answer from the GlobalCoordinator
		Message<?> msg = Messages.receiveSynWithError(this, gcID, giID);
		if (msg == null) {
			HermesLogger.log(HermesLogger.ERROR, this.getAgentName()
					+ " - unable to correctly communicate with "
					+ "the GlobalCoordinator");
			Messages.sendAck(this, giID);
		} else {
			Object[] params = { LocalCoordinator.class.getSimpleName(), gcID,
					smID, giID };
			createAgent(LocalCoordinator.class, params);
		}
		done = true;
	}
}